﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using Castle.Core;
using Castle.MicroKernel.Registration;
using Castle.Windsor;
using Dexter.Dependency.Castle.Resolver;
using Gaia.Alp.Dependency.Castle.Configuration;

namespace Dexter.Dependency.Castle {
	/// <summary>
	/// 	The implementation of <see cref="IDexterContainer" /> using <c>Caslte Windsor</c> container. see also http://www.castleproject.org/container/ />.
	/// </summary>
	public class CastleContainer : IDexterContainer {
		IWindsorContainer container;

		/// <summary>
		/// 	Initializes a new instance of the <see cref="CastleContainer" /> class.
		/// </summary>
		public CastleContainer() {
			Container = new WindsorContainer();
		}

		/// <summary>
		/// 	Initializes a new instance of the <see cref="CastleContainer" /> class.
		/// </summary>
		/// <param name="container"> The container. </param>
		public CastleContainer(IWindsorContainer container) {
			Container = container;
		}

		public IWindsorContainer Container {
			get { return overrideContainer ?? container; }
			set { container = value; }
		}

		#region IDexterContainer Members

		/// <summary>
		/// 	Dispose the container.
		/// </summary>
		public void Shutdown() {
			if (Container != null) {
				Container.Dispose();
				Container = null;
			}
		}

		/// <summary>
		/// 	Configures the container wirth the specified file path.
		/// </summary>
		/// <param name="filePath"> The filePath. </param>
		public void Configure(string filePath) {
			if (!string.IsNullOrEmpty(filePath) && File.Exists(filePath))
				Container.Install(global::Castle.Windsor.Installer.Configuration.FromXmlFile(filePath));
			else
				Container.Install();

			container.Kernel.Resolver.AddSubResolver(new LoggerSubDependencyResolver(container.Kernel));
			CastleConfiguration.RegisterInterceptor(Container);
		}

		/// <summary>
		/// 	Retrieve the instance for the specified <c>type</c> .
		/// </summary>
		/// <param name="type"> The type. </param>
		/// <returns> The registered instance or null if the instance will not fund. </returns>
		public object Resolve(Type type) {
			return Container.Resolve(type);
		}

		/// <summary>
		/// 	Retrieve the instance for the specified <c>type</c> with the specified registration name.
		/// </summary>
		/// <param name="type"> The type of the object to retrieve. </param>
		/// <param name="key"> The name of the registered instance. </param>
		/// <returns> The registered instance or null if the instance will not fund. </returns>
		public object Resolve(Type type, string key) {
			return Container.Resolve(type, key);
		}

		/// <summary>
		/// 	Resolves this instance.
		/// </summary>
		/// <typeparam name="T"> </typeparam>
		/// <returns> </returns>
		public T Resolve<T>() {
			return Container.Resolve<T>();
		}

		/// <summary>
		/// 	Resolves the specified key.
		/// </summary>
		/// <typeparam name="T"> </typeparam>
		/// <param name="key"> The key. </param>
		/// <returns> </returns>
		public T Resolve<T>(string key) {
			return Container.Resolve<T>(key);
		}

		/// <summary>
		/// 	Registers a new <c>type</c> to the container.
		/// </summary>
		/// <param name="key"> The name of the specified type. </param>
		/// <param name="type"> The type to register. </param>
		/// <param name="instance"> The instance of <paramref name="type" /> . </param>
		/// <param name="lifeCycle"> The life cycle. </param>
		/// <remarks>
		/// 	Singleton should be the default lifecycle.
		/// </remarks>
		public void Register(string key, Type type, object instance, LifeCycle lifeCycle) {
			ComponentRegistration<object> component;
			switch (lifeCycle) {
				case LifeCycle.Transient:
					component = Component.For(type)
						.Instance(instance)
						.Named(key)
						.LifeStyle.Is(LifestyleType.Transient);
					break;
				case LifeCycle.Thread:
					component = Component.For(type)
						.Instance(instance)
						.Named(key)
						.LifeStyle.Is(LifestyleType.Thread);
					break;
				default:
					component = Component.For(type)
						.Instance(instance)
						.Named(key)
						.LifeStyle.Is(LifestyleType.Singleton);
					break;
			}

			Container.Register(component);
		}

		/// <summary>
		/// 	Registers a new <c>type</c> to the container.
		/// </summary>
		/// <param name="key"> The name of the specified type. </param>
		/// <param name="type"> The type to register. </param>
		/// <param name="implementedBy"> The implementation of <paramref name="type" /> . </param>
		/// <param name="lifeCycle"> The life cycle. </param>
		/// <remarks>
		/// 	Singleton should be the default lifecycle.
		/// </remarks>
		public void Register(string key, Type type, Type implementedBy, LifeCycle lifeCycle) {
			ComponentRegistration<object> component;
			switch (lifeCycle) {
				case LifeCycle.Transient:
					component = Component.For(type)
						.ImplementedBy(implementedBy)
						.Named(key)
						.LifeStyle.Is(LifestyleType.Transient);
					break;
				case LifeCycle.Thread:
					component = Component.For(type)
						.ImplementedBy(implementedBy)
						.Named(key)
						.LifeStyle.Is(LifestyleType.Thread);
					break;
				default:
					component = Component.For(type)
						.ImplementedBy(implementedBy)
						.Named(key)
						.LifeStyle.Is(LifestyleType.Singleton);
					break;
			}

			Container.Register(component);
		}

		/// <summary>
		/// 	Registers the specified key.
		/// </summary>
		/// <typeparam name="T"> </typeparam>
		/// <typeparam name="TK"> The type of the K. </typeparam>
		/// <param name="key"> The key. </param>
		/// <param name="instance"> The instance. </param>
		/// <param name="lifeCycle"> The life cycle. </param>
		public void Register<T, TK>(string key, TK instance, LifeCycle lifeCycle) where TK : class, T {
			Register(key, typeof (T), instance, lifeCycle);
		}

		/// <summary>
		/// 	Registers the specified key.
		/// </summary>
		/// <typeparam name="T"> </typeparam>
		/// <typeparam name="TK"> The type of the K. </typeparam>
		/// <param name="key"> The key. </param>
		/// <param name="lifeCycle"> The life cycle. </param>
		public void Register<T, TK>(string key, LifeCycle lifeCycle) where TK : class, T {
			Register(key, typeof(T), typeof(TK), lifeCycle);
		}

		/// <summary>
		/// 	Registers the components by base class.
		/// </summary>
		/// <typeparam name="T"> </typeparam>
		/// <param name="assembly"> The assembly. </param>
		/// <param name="lifeCycle"> The life cycle. </param>
		public void RegisterComponentsByBaseClass<T>(Assembly assembly, LifeCycle lifeCycle) {
			Type baseClass = typeof (T);

			IEnumerable<Type> types = from c in assembly.GetTypes()
			                                where !c.IsAbstract && baseClass.IsAssignableFrom(c)
			                                select c;

			foreach (Type type in types) {
				if (type == null || string.IsNullOrEmpty(type.FullName)) {
					continue;
				}

				Register(type.FullName.ToLowerInvariant(), type, type, lifeCycle);
			}
		}

		public void AddRegistration(string key, Type type, object instance, LifeCycle lifeCycle) {
			Register(key, type, instance, lifeCycle);
		}

		public void AddRegistration(string key, Type type, Type implementedBy, LifeCycle lifeCycle) {
			Register(key, type, implementedBy, lifeCycle);
		}

		public void AddRegistration<T, TK>(string key, TK instance, LifeCycle lifeCycle) where TK : class, T {
		ComponentRegistration<object> component;
			switch (lifeCycle) {
				case LifeCycle.Transient:
					component = Component.For(typeof (T))
						.ImplementedBy(typeof (TK))
						.Instance(instance)
						.Named(key)
						.LifeStyle.Is(LifestyleType.Transient);
					break;
				case LifeCycle.Thread:
					component = Component.For(typeof (T))
						.ImplementedBy(typeof (TK))
						.Instance(instance)
						.Named(key)
						.LifeStyle.Is(LifestyleType.Thread);
					break;
				default:
					component = Component.For(typeof (T))
						.ImplementedBy(typeof (TK))
						.Instance(instance)
						.Named(key)
						.LifeStyle.Is(LifestyleType.Singleton);
					break;
			}

			Container.Register(component);
		}

		public void AddRegistration<T, TK>(string key, LifeCycle lifeCycle) where TK : class, T {
			Register(key, typeof(T),typeof(TK), lifeCycle);
		}

		public void AddRegistrationComponentsByBaseClass<T>(Assembly assembly, LifeCycle lifeCycle) {
			RegisterComponentsByBaseClass<T>(assembly, lifeCycle);
		}

		/// <summary>
		/// 	Retrieve all registered instances for the specified type.
		/// </summary>
		/// <typeparam name="T"> The type of the objects to resolve. </typeparam>
		/// <returns> </returns>
		public T[] ResolveAll<T>() {
			return Container.ResolveAll<T>();
		}

		/// <summary>
		/// 	Retrieve all registered instances for the specified <param name="type" /> .
		/// </summary>
		/// <param name="type"> The type of the objects to resolve. </param>
		/// <returns> Null if there aren't registered instances for the specified <c>type</c> . Otherwise an array of <c>object</c> . </returns>
		public object[] ResolveAll(Type type) {
			return (object[]) Container.ResolveAll(type);
		}

		#endregion

		#region Overriding container for test

		IWindsorContainer overrideContainer;

		internal IDisposable OverrideContainer(IWindsorContainer windsorContainer) {
			overrideContainer = windsorContainer;
			return new DisposableAction(() => overrideContainer = null);
		}

		#endregion
	}
}